<!--
@license
Copyright 2017 The TensorFlow Authors. All Rights Reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->

<link rel="import" href="../tf-dashboard-common/dashboard-style.html">
<link rel="import" href="../paper-dropdown-menu/paper-dropdown-menu.html">
<link rel="import" href="../paper-item/paper-item.html">
<link rel="import" href="../paper-menu/paper-menu.html">
<link rel="import" href="../polymer/polymer.html">
<link rel="import" href="../tf-backend/tf-backend.html">
<link rel="import" href="../tf-dashboard-common/tf-dashboard-layout.html">
<link rel="import" href="../tf-graph-controls/tf-graph-controls.html">
<link rel="import" href="../tf-input-pipeline/input-pipeline-analyzer.html">
<link rel="import" href="../tf-overview-page/overview-page.html">
<link rel="import" href="../tf-op-profile/tf-op-profile.html">
<link rel="import" href="../tf-tensorboard/registry.html">
<link rel="import" href="../vz-sorting/vz-sorting.html">

<!--
tf-profile-dashboard displays profiling information for different tools.

In the profile dashboard, a "run" is a profile run and "tag" is a tool name. A
profile run can have multiple tools that present the performance profile as different visualizations
(e.g. Catapult TraceViewer).
-->

<dom-module id="tf-profile-dashboard">
<template>
<template is="dom-if" if="[[_dataNotFound]]">
  <div style="max-width: 540px; margin: 80px auto 0 auto;">
    <h3>No profile data was found.</h3>
    <p>To collect a profile, you need to run your model on Google Cloud TPUs and
    capture the trace information while your model is running. You may want to
    check out the
    <a href="https://github.com/tensorflow/tensorboard/blob/1.6/tensorboard/plugins/profile/README.md">README</a>
    and perhaps the
    <a
     href="https://cloud.google.com/tpu/docs/cloud-tpu-tools"
    >tutorial</a> on how to use the
    <a href="https://pypi.python.org/pypi/cloud-tpu-profiler">cloud-tpu-profiler</a>.
    </p>
    <p>
    If you’re new to TPUs, and want to find out how
    to run models, check out the
    <a href="https://cloud.google.com/tpu/docs/quickstart">Quickstart Using a TPU</a>.
    </p>
    <p>
    If you think profiling is done properly, please see the page of
    <a href="https://cloud.google.com/tpu/docs/troubleshooting">Google Cloud TPU Troubleshooting and FAQ</a>
    and consider filing an issue on GitHub.</p>
  </div>
</template>
<template is="dom-if" if="[[!_dataNotFound]]">
  <tf-dashboard-layout>
  <div class="sidebar">
    <div class="allcontrols">
      <div class="sidebar-section">
        <div class="title">Runs <span class="counter">([[_datasets.length]])</span></div>
        <paper-dropdown-menu no-label-float no-animations noink class="run-dropdown">
          <paper-listbox id="list_box_run" class="dropdown-content" selected="{{selectedDatasetIndex}}">
            <template id="run_items" is="dom-repeat" items="[[_datasets]]">
              <paper-item>[[item.name]]</paper-item>
            </template>
          </paper-listbox>
        </paper-dropdown-menu>
      </div>
      <div class="sidebar-section">
        <div class="title">Tools <span class="counter">([[_activeToolsList.length]])</span></div>
        <paper-dropdown-menu no-label-float no-animations noink class="run-dropdown">
          <paper-listbox id="list_box_tool" class="dropdown-content" selected="{{selectedToolIndex}}">
            <template id="tool_items" is="dom-repeat" items="[[_activeToolsList]]">
              <paper-item>[[item]]</paper-item>
            </template>
            <template is="dom-if" if="[[!_hasActiveTools()]]" restamp="true">
              <paper-item>None</paper-item>
            </template>
          </paper-listbox>
        </paper-dropdown-menu>
      </div>
      <div class="sidebar-section">
        <div class="title">Hosts <span class="counter">([[_activeHostsList.length]])</span></div>
        <paper-dropdown-menu no-label-float no-animations noink class="run-dropdown">
          <paper-listbox id="list_box_host" class="dropdown-content" selected="{{selectedHostIndex}}">
            <template id="host_items" is="dom-repeat" items="[[_activeHostsList]]">
              <paper-item>[[_getHostDisplayName(item)]]</paper-item>
            </template>
          </paper-listbox>
        </paper-dropdown-menu>
      </div>
      <div class="sidebar-section"></div>
    </div>
  </div>
  <div class="center">
    <template is="dom-if" if="[[_isCurrentTool(_toolInScope, 'trace_viewer')]]" restamp="true">
      <iframe
        id="tv_iframe"
        height="100%"
        width="100%"
        src="[[_traceDataUrl]]">
      </iframe>
    </template>
    <template is="dom-if" if="[[_isCurrentTool(_toolInScope, 'op_profile')]]" restamp="true">
      <tf-op-profile _data="[[_opProfileData]]"></tf-op-profile>
    </template>
    <template is="dom-if" if="[[_isCurrentTool(_toolInScope, 'input_pipeline_analyzer')]]" restamp="true">
      <input-pipeline-analyzer _data="[[_inputPipelineData]]"></input-pipeline-analyzer>
    </template>
    <template is="dom-if" if="[[_isCurrentTool(_toolInScope, 'overview_page')]]" restamp="true">
      <overview-page _data="[[_overviewPageData]]"></overview-page>
    </template>
  </div>
  </tf-dashboard-layout>
</template>
<style include="dashboard-style"></style>

<style>
  .center {
    position: relative;
    height: 100%;
  }
  iframe {
    position: absolute;
    width: 100%;
    height: 100%;
    box-sizing: border-box;
  }
</style>

</template>
<script>
  "use strict";

  Polymer({
    is: "tf-profile-dashboard",
    properties: {
      _requestManager: {
        type: Object,
        value: () => new tf_backend.RequestManager(),
      },
      _isAttached: Boolean,
      // Whether this dashboard is initialized. This dashboard should only be
      // initialized once.
      _initialized: Boolean,
      _dataNotFound: Boolean,
      _datasets: {
        type: Array,
        notify: true,
        observer: '_datasetsChanged'
      },
      _activeToolsList: {
        type: Array,
        computed: '_getActiveToolsList(selectedDatasetIndex, _datasets)',
        observer: '_activeToolsChanged'
      },
      _activeHostsList: {
        type: Array,
        observer: '_activeHostsChanged'
      },
      selectedDatasetIndex: {
        type: Number,
        notify: true,
        value: 0,
      },
      selectedToolIndex: {
        type: Number,
        notify: true,
        value: 0,
      },
      selectedHostIndex: {
        type: Number,
        notify: true,
        value: 0,
      },
      // The endpoint that serves trace viewer app.
      traceViewerBaseUrl: {
        type: String,
        value: "/trace_viewer_index.html",
      },
      // The URL for the trace data being display.
      _traceDataUrl: {
        type: String,
        value: "",
      },
      _opProfileData: {
        type: Object,
      },
      _inputPipelineData: {
        type: Object,
      },
      _overviewPageData: {
        type: Object,
      },
      _selectedDatasetName: {
        type: String,
        notify: true,
        computed: '_getSelectedDatasetName(selectedDatasetIndex, _datasets)'
      },
      _selectedToolName: {
        type: String,
        notify: true,
        computed: '_getSelected(selectedToolIndex, _activeToolsList)'
      },
      _selectedHostName: {
        type: String,
        notify: true,
        computed: '_getSelected(selectedHostIndex, _activeHostsList)'
      },
      _toolInScope: {
        type: String,
      },
    },
    reload: function() {
    },
    ready: function() {
    },
    observers: [
      '_maybeInitializeDashboard(_isAttached)',
      '_maybeUpdateData(_selectedHostName)',
      '_maybeUpdateActiveHosts(_selectedDatasetName, _selectedToolName)'
    ],
    attached: function() {
      this.set('_isAttached', true);
    },
    detached: function() {
      this.set('_isAttached', false);
    },
    _maybeInitializeDashboard: function(isAttached) {
      if (this._initialized || !isAttached) {
        // Either this dashboard is already initialized ... or we are not yet
        // ready to initialize.
        return;
      }
      // Set this to true so we only initialize once.
      this._initialized = true;
      const profileTagsURL =
        tf_backend.getRouter().pluginRoute('profile', '/tools') +
        (tf_backend.getRouter().isDemoMode() ? '.json' : '');
      this._requestManager.request(profileTagsURL).then((runToTool) => {
        var datasets = _.map(runToTool, (tools, run) => ({
          name: run,
          activeTools: tools,
        }));
        datasets.sort((a, b) => {
          // The run name is currently a timestamp like "YYYY-MM-DD_HH:MM:SS".
          // Sort runs by timestamp in reversed order so that the latest run
          // comes first.
          return 0 - vz_sorting.compareTagNames(a.name, b.name);
        });
        this.set('_dataNotFound', datasets.length === 0);
        this.set('_datasets', datasets);
      });
    },
    // Return the item selected from the list
    _getSelected: function(index, li) {
      if (index == null) return;
      if (li && index >= 0 && index < li.length) {
        return li[index];
      }
      return null;
    },
    _getSelectedDatasetName: function(selectedDatasetIndex, datasets){
      if (selectedDatasetIndex == null) return;
      if (datasets && selectedDatasetIndex >= 0 && selectedDatasetIndex < datasets.length) {
        return datasets[selectedDatasetIndex].name;
      }
      return '';
    },
     _getActiveToolsList: function(selectedDatasetIndex, datasets) {
      if (selectedDatasetIndex == null) return;
      if (datasets && selectedDatasetIndex >= 0 && selectedDatasetIndex < datasets.length) {
        this.selectedToolIndex = 0;
        return datasets[selectedDatasetIndex].activeTools;
      }
      return [];
    },
    _maybeUpdateData: function(hostName) {
      if (hostName == null) return;
      var datasetName = this._selectedDatasetName;
      var toolName = this._selectedToolName;
      if (datasetName == null || toolName == null) return;
      this.toolInScope = "undefined";
      if (toolName.startsWith("trace_viewer")) {
        var trace_data_url = tf_backend.addParams(
            tf_backend.getRouter().pluginRoute('profile', '/data'),
            {tag: toolName,
             run: datasetName,
             host: hostName});
        // Make the trace data url relative to the root.
        if (trace_data_url[0] != '/') {
          trace_data_url = '/' + trace_data_url;
        }
        // Pass the URL of trace data via GET parameter to the iframed trace
        // viewer app.
        var is_streaming = toolName.endsWith('@');
        this._traceDataUrl = this.traceViewerBaseUrl + '?trace_data_url=' +
            encodeURIComponent(trace_data_url) +
            '&is_streaming=' + is_streaming;
        this._toolInScope = "trace_viewer";
        return;
      } else {
        this._requestManager.request(tf_backend.addParams(
          tf_backend.getRouter().pluginRoute('profile', '/data'),
          {tag: toolName, host: hostName, run: datasetName})
        ).catch(error => {}
        ).then((data) => {
           if (toolName == "op_profile") {
               this._opProfileData = data;
               this._toolInScope = "op_profile";
           } else if (toolName == "input_pipeline_analyzer") {
               this._inputPipelineData = data;
               this._toolInScope = "input_pipeline_analyzer";
           } else if (toolName == "overview_page") {
               this._overviewPageData = data;
               this._toolInScope = "overview_page";
           }
        });
        return;
      }
    },
    _maybeUpdateActiveHosts: function(datasetName, toolName) {
       if(datasetName == null || toolName == null) return null;
       var profileHostsUrl = tf_backend.addParams(
            tf_backend.getRouter().pluginRoute('profile', '/hosts'),
            {tag: toolName,
             run: datasetName,
            });
       this._requestManager.request(profileHostsUrl).then((hostList) => {
           this.set('_activeHostsList', hostList.sort((a, b) => {
               return vz_sorting.compareTagNames(a, b);}
           ));
       });
    },
    _datasetsChanged: function(newDatasets, oldDatasets) {
      if (this._datasets) {
        this.selectedDatasetIndex = 0;
      }
    },
    _activeToolsChanged: function(oldActiveTools, newActiveTools) {
      // Same tool can have differernt index in different runs, therefore we force a change of
      // 'selectedToolIndex', to make sure the label of the dropdown-menu is synced.
      if (this._activeToolsList) {
         this.async(function() {
          this.set('selectedToolIndex', -1);
          this.set('selectedToolIndex', 0);
        }.bind(this));
      }
    },
    _activeHostsChanged: function(oldActiveHosts, newActiveHosts) {
      if (this._activeHostsList) {
        this.async(function() {
          this.set('selectedHostIndex', -1);
          this.set('selectedHostIndex', 0);
        }.bind(this));
      }
    },
    _isCurrentTool: function(current, expected) {
      return current == expected;
    },
    _hasActiveTools: function() {
      if (this._activeToolsList && this._activeToolsList.length > 0) {
        return true;
      }
      return false;
    },
    _getHostDisplayName: function(host) {
      // For old traces without a host prefix in file name, return default for
      // backward compatibility.
      if (host == null) return "";
      if (host == "") return "default";
      // Otherwise, remove the seperator, e.g. "host1_" => "host1".
      return host.slice(0, -1);
    },
  });

  tf_tensorboard.registerDashboard({
    plugin: 'profile',
    elementName: 'tf-profile-dashboard',
    isReloadDisabled: true,
  });

</script>
</dom-module>
